package de.joergviola.mobj.client.database;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.code.gwt.database.client.Database;
import com.google.code.gwt.database.client.SQLError;
import com.google.code.gwt.database.client.SQLTransaction;
import com.google.gwt.user.client.rpc.AsyncCallback;

import de.joergviola.mobj.client.ITransaction;
import de.joergviola.mobj.shared.Entity;
import de.joergviola.mobj.shared.Filter;
import de.joergviola.mobj.shared.KeyNotUniqueException;
import de.joergviola.mobj.shared.ObjectNotFoundException;
import de.joergviola.mobj.shared.Persistable;
import de.joergviola.mobj.shared.Query;
import de.joergviola.mobj.shared.Schema;
import de.joergviola.mobj.shared.UnknownEntityException;

public class AsyncDBTransaction implements ITransaction {

	private static final Logger log = Logger.getLogger("DBTransaction");
	private final Database db;
	private final Schema schema;

	public AsyncDBTransaction(Database db, Schema schema) {
		this.db = db;
		this.schema = schema;
	}

	public <T extends Persistable> void insert(final T t,
			final AsyncCallback<T> callback) {
		db.transaction(new BaseTransaction() {
			@Override
			public void onTransactionStart(SQLTransaction tx) {
				Entity<T> entity = schema.getEntity(t);
				if (entity == null) {
					callback.onFailure(new UnknownEntityException(t.getClass()
							.getName()));
					return;
				}
				Insert<T> insert = new Insert<T>(schema, entity, t);
				try {
					insert.execute(tx);
				} catch (UnknownEntityException e) {
					callback.onFailure(e);
					return;
				}
				callback.onSuccess(t);
			}

			@Override
			public void onTransactionFailure(SQLError error) {
				callback.onFailure(new Exception(error.getMessage()));
			}
		});
	}

	public <T extends Persistable> void update(final T t,
			final AsyncCallback<Void> callback) {
		db.transaction(new BaseTransaction() {
			@Override
			public void onTransactionStart(SQLTransaction tx) {
				Entity<T> entity = schema.getEntity(t);
				if (entity == null) {
					callback.onFailure(new UnknownEntityException(t.getClass()
							.getName()));
					return;
				}
				Update<T> update = new Update<T>(schema, entity, t);
				try {
					update.execute(tx);
				} catch (UnknownEntityException e) {
					callback.onFailure(e);
					return;
				}
				callback.onSuccess(null);
			}

			@Override
			public void onTransactionFailure(SQLError error) {
				callback.onFailure(new Exception(error.getMessage()));
			}
		});
	}

	private <T extends Persistable> String getIdWhere(Entity<T> entity) {
		String idField = entity.getFieldnames()[0];
		String where = idField + "=?";
		return where;
	}

	@Override
	public <T extends Persistable> void get(final String className,
			final Long id, final AsyncCallback<T> callback) {
		log.info("get by id: " + className + "@" + id);
		db.transaction(new BaseTransaction() {
			@Override
			public void onTransactionStart(SQLTransaction tx) {
				@SuppressWarnings("unchecked")
				Entity<T> entity = (Entity<T>) schema.getEntity(className);
				if (entity == null) {
					callback.onFailure(new UnknownEntityException(className));
					return;
				}
				Get<T> get = new Get<T>(schema, entity);
				get.execute(tx, id, callback);
				// String where = getIdWhere(entity);
				// Select<T> select = new Select<T>(entity, where);
				// select.setArgs(new Object[] { id });
				// select.execute(tx, new AsyncCallback<List<T>>() {
				//
				// @Override
				// public void onFailure(Throwable caught) {
				// log.log(Level.SEVERE, "", caught);
				// callback.onFailure(caught);
				// }
				//
				// @Override
				// public void onSuccess(List<T> result) {
				// if (result.size() == 0) {
				// log.info("Object not found");
				// callback.onFailure(new ObjectNotFoundException(id));
				// } else if (result.size() > 1) {
				// log.info("Non-unique key");
				// callback.onFailure(new KeyNotUniqueException(id,
				// result.size()));
				// } else {
				// callback.onSuccess(result.get(0));
				// }
				// }
				// });
			}

			@Override
			public void onTransactionFailure(SQLError error) {
				callback.onFailure(new Exception(error.getMessage()));
			}
		});
	}

	@Override
	public <T extends Persistable> void get(final String className,
			final List<Long> ids, final AsyncCallback<List<T>> callback) {
		if (ids == null || ids.size() == 0) {
			callback.onSuccess(new ArrayList<T>());
		}
		db.transaction(new BaseTransaction() {
			@Override
			public void onTransactionStart(SQLTransaction tx) {
				@SuppressWarnings("unchecked")
				Entity<T> entity = (Entity<T>) schema.getEntity(className);
				if (entity == null) {
					callback.onFailure(new UnknownEntityException(className));
					return;
				}
				String idField = entity.getFieldnames()[0];
				String where = idField + "=?";
				Select<T> select = new Select<T>(schema, entity, where);
				Object[] args = new Object[1];
				final ArrayList<T> list = new ArrayList<T>();
				for (final Long id : ids) {
					args[0] = id;
					select.setArgs(args);
					select.execute(tx, new AsyncCallback<List<T>>() {

						@Override
						public void onFailure(Throwable caught) {
							// Maybe called multiple times!
							log.log(Level.SEVERE, "", caught);
							callback.onFailure(caught);
						}

						@Override
						public void onSuccess(List<T> result) {
							if (result.size() == 0) {
								callback.onFailure(new ObjectNotFoundException(
										id));
								return;
							} else if (result.size() > 1) {
								callback.onFailure(new KeyNotUniqueException(
										id, result.size()));
								return;
							} else {
								list.add(result.get(0));
							}
							if (list.size() == ids.size()) {
								// Called max once
								log.info("Objects found: " + list.size());
								callback.onSuccess(list);
							}
						}
					});
				}
			}

			@Override
			public void onTransactionFailure(SQLError error) {
				callback.onFailure(new Exception(error.getMessage()));
			}
		});
	}

	@Override
	public <T extends Persistable> void find(final String className,
			final Query query, final AsyncCallback<List<T>> callback) {
		db.transaction(new BaseTransaction() {
			@Override
			public void onTransactionStart(SQLTransaction tx) {
				@SuppressWarnings("unchecked")
				Entity<T> entity = (Entity<T>) schema.getEntity(className);
				if (entity == null) {
					callback.onFailure(new UnknownEntityException(className));
					return;
				}
				ArrayList<Object> args = new ArrayList<Object>();
				StringBuilder where = new StringBuilder();
				String sep = "";
				for (Filter filter : query.getFilter()) {
					where.append(sep);
					where.append(filter.getField());
					where.append("=?");
					args.add(filter.getValue());
					sep = " AND ";
				}
				Select<T> select = new Select<T>(schema, entity, where
						.toString());
				Object[] argArray = new Object[args.size()];
				args.toArray(argArray);
				select.setArgs(argArray);
				select.execute(tx, callback);
			}

			@Override
			public void onTransactionFailure(SQLError error) {
				callback.onFailure(new Exception(error.getMessage()));
			}
		});
	}

	@Override
	public <T extends Persistable> void delete(final T t,
			final AsyncCallback<Void> callback) {
		db.transaction(new BaseTransaction() {
			@Override
			public void onTransactionStart(SQLTransaction tx) {
				Entity<T> entity = (Entity<T>) schema.getEntity(t);
				if (entity == null) {
					callback.onFailure(new UnknownEntityException(t.getClass()
							.getName()));
					return;
				}
				String where = getIdWhere(entity);
				Delete<T> delete = new Delete<T>(entity, where);
				Object[] args = new Object[1];
				delete.setArgs(args);
				delete.execute(tx);
				callback.onSuccess(null);
			}

			@Override
			public void onTransactionFailure(SQLError error) {
				callback.onFailure(new Exception(error.getMessage()));
			}
		});
	}
}
